#include "digitalWriteFast.h"

//#define debug
//#define debugCardRead
// #define debugWiegand



//data structure for door info
struct doorInterface {
    unsigned char pinNum;
    unsigned char newState;
    unsigned long updateTime;
};

//door pin definitions
#define NUM_DOORS 3
#define D_0_PIN  13
#define D_1_PIN  12
#define D_2_PIN  11
//relate door number 
//doorInterface doorData[NUM_DOORS];// = { D_0_PIN, D_1_PIN, D_2_PIN };
#ifdef debugWiegand
#define wiegDBGPin  
#endif


#define ULOCK_STATE LOW
#define LOCK_STATE  HIGH


//Start Global Variables
char cmd;                               //variable to store incomming command from the serial port
unsigned long cardNum = 0;              //variables to store the transmitted Card Number
unsigned int doorNum = 0;               //var to store the door number from serial port

//external variables
extern volatile unsigned long timer0_overflow_count;  //millis() counter reset

doorInterface doorData[NUM_DOORS];
//End Global Variables

const int RFID_ACTIVE_PIN = 2;  //RFID_active debug pin
//Reader 0
const int R0PinNum0 = 5;
const int R0PinNum1 = 4;
//Reader 1
const int R1PinNum0 = 7;
const int R1PinNum1 = 6;
//Reader 2
const int R2PinNum0 = 9;
const int R2PinNum1 = 8;

void setup()
{
  int k;
  
  Serial.begin(115200);

  pinModeFast(RFID_ACTIVE_PIN, OUTPUT); 
  pinModeFast(R0PinNum0, INPUT);
  pinModeFast(R0PinNum1, INPUT);
  pinModeFast(R1PinNum0, INPUT);
  pinModeFast(R1PinNum1, INPUT);
  pinModeFast(R2PinNum0, INPUT);
  pinModeFast(R2PinNum1, INPUT);
  //set pullups for door interface inputs
  digitalWriteFast(R0PinNum0,HIGH);
  digitalWriteFast(R0PinNum1,HIGH);
  digitalWriteFast(R1PinNum0,HIGH);
  digitalWriteFast(R1PinNum1,HIGH);
  digitalWriteFast(R2PinNum0,HIGH);
  digitalWriteFast(R2PinNum1,HIGH);
  
  pinModeFast(D_0_PIN, OUTPUT);
  pinModeFast(D_1_PIN, OUTPUT);
  pinModeFast(D_2_PIN, OUTPUT);
  
  
  //add door info
  doorData[0].pinNum = D_0_PIN;
  doorData[1].pinNum = D_1_PIN;  
  doorData[2].pinNum = D_2_PIN;  
  //default new states to LOCK_STATE
  doorData[0].newState = LOCK_STATE; 
  doorData[1].newState = LOCK_STATE;
  doorData[2].newState = LOCK_STATE;
  //reset timer counteer to make sure doors are updated 
  timer0_overflow_count = 0;
  //set newTime 0 to show no udpate pending
  doorData[0].updateTime = 10;
  doorData[1].updateTime = 10;
  doorData[2].updateTime = 10;
  
  //  
  
}



#define val0Bits     8
#define val1Bits     16
#define uSBitPeriod  2050//d1075
#define MAX_TIMEOUT  2000

unsigned long readRFID( int D0pin, int D1pin )
{
   unsigned char val0 = 0;                         //variable to store the building code
   unsigned int val1 = 0;                          //variable to store the card number
   unsigned long result = 0;                       //variable to store the result
   int i;                                          //loop index
   int timeout = 0;
   boolean comTimeout = 0;                         //var to show read failure
   boolean D0state,D1state;                        //variables to store pin states
   
   //this function is called because on of the signal pins for the reader went low, check to make sure the signal is valid
   //both signals should not be in the same state
   //start by reading the pin states
   D0state = digitalReadFast(D0pin);
   D1state = digitalReadFast(D1pin);
   
   if( D0state == D1state )
   {
#ifdef debug
      //not valid signal state, give up
      Serial.println("RFID read Failed at start, D0pin == D1pin");
#endif
      return 0;
   }
   //start by throwing out the 1st bit for now since it is the even parity bit, and we are not doing parity check

   //loop though remaining bits
   //start by reading 8-bit facility code
   
   for( i=0;i<val0Bits;i++)
   {
     
     //wait for signals to return to neutral
     while( ( D0state != D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;
       
     }
     //next delay for 1 bit period then read state
     timeout = 0;     
     delayMicroseconds( uSBitPeriod );
     //read bit state
#ifdef debugWiegand
     digitalWriteFast( RFID_ACTIVE_PIN, HIGH);
#endif
       
     D0state = digitalReadFast(D0pin);
     D1state = digitalReadFast(D1pin);  
     
     //check that they are in a valid state, if not then give up
     if( D0state == 1 && D1state == 0 )
     {
       //valid bit is logic 1
       val0 = val0<<1;
       val0++;                //shift bits up 1
       
     }
     else if( D0state ==0 && D1state == 1 )
     {
       //valid bit, logic 0 
       val0 = val0<<1;                    //shift bits up 1
     }
     else
     {
#ifdef debug
       Serial.print("RFID val0 failed D1state == D0state at loop index ");
       Serial.println(i,DEC);
       if(digitalReadFast(D0pin) == HIGH )
         Serial.println("D0state = 1");
       if(digitalReadFast(D1pin) == HIGH )
         Serial.println("D1state = 1");
       if(D0state)
         Serial.println("D0state = 1");
       if(D1state)
         Serial.println("D1state = 1");
#endif
       return 0;
     } 
     
#ifdef debugWiegand
     digitalWriteFast( RFID_ACTIVE_PIN, LOW);
#endif
     
   }  
    
   //next, read the seconds value (16-bits)  
   for( i=0;i<val1Bits;i++)
   {    
     
     //wait for signals to return to neutral
     timeout = 0;
     while( ( D0state != D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;
       
     }
     //next wait for next bit transition
     timeout = 0;
     delayMicroseconds( uSBitPeriod );
     
#ifdef debugWiegand
     digitalWriteFast( RFID_ACTIVE_PIN, HIGH);
#endif
     //read bit state
       
     D0state = digitalReadFast(D0pin);
     D1state = digitalReadFast(D1pin);  
     
     
    //check that they are in a valid state, if not then give up
     if( D0state == 1 && D1state == 0 )
     {
       //valid bit is logic 1
       val1 = val1<<1;
       val1++;                //shift bits up 1
       
     }
     else if( D0state ==0 && D1state == 1 )
     {
       //valid bit, logic 0 
       val1 = val1<<1;                    //shift bits up 1
     }
     else
     {
#ifdef debug
       Serial.print("RFID val1 failed D1state == D0state at loop index ");
       Serial.println(i,DEC);
#endif
       return 0;
     }
     //waste time before the next bit
     //delayMicroseconds( uSBitPeriod );
     if( comTimeout )
     {
 #ifdef debug
       Serial.print(" RFIDread timed out on index ");
       Serial.println(i,DEC);
 #endif
       return 0;
    
     } 
   
#ifdef debugWiegand
     digitalWriteFast( RFID_ACTIVE_PIN, LOW);
#endif  
   } 
   //if we have gotten this far, then we have 24 valid bits, we won't bother reading the last bit which is the
   //second parity bit 
   //calculate the result
#ifdef debug2
   Serial.println("Finished reading RFID");
   Serial.println(val0,DEC);
   Serial.println(val1,DEC);
#endif
   result = val0;
   result = result<<16;                            //shift the 1st value bits up to make room
   //val1 = val1>>1;                                 //ignore the last bit
   result += val1;                              
   return result;   
  
}


/*
unsigned long readRFID( int D0pin, int D1pin )
{
   unsigned char val0 = 0;                         //variable to store the building code
   unsigned int val1 = 0;                          //variable to store the card number
   unsigned long result = 0;                       //variable to store the result
   int i;                                          //loop index
   int timeout = 0;
   boolean comTimeout = 0;                         //var to show read failure
   boolean D0state,D1state;                        //variables to store pin states
   
   //this function is called because on of the signal pins for the reader went low, check to make sure the signal is valid
   //both signals should not be in the same state
   //start by reading the pin states
   D0state = digitalReadFast(D0pin);
   D1state = digitalReadFast(D1pin);
   
   if( D0state == D1state )
   {
#ifdef debug
      //not valid signal state, give up
      Serial.println("RFID read Failed at start, D0pin == D1pin");
#endif
      return 0;
   }
   //start by throwing out the 1st bit for now since it is the even parity bit, and we are not doing parity check

   //loop though remaining bits
   //start by reading 8-bit facility code
   
   for( i=0;i<val0Bits;i++)
   {
     
     //wait for signals to return to neutral
     while( ( D0state != D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;
       
     }
     //next wait for next bit transition
     timeout = 0;
     while( ( D0state == D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);  
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;  
     }
     
     //check that they are in a valid state, if not then give up
     if( D0state == 1 && D1state == 0 )
     {
       //valid bit is logic 1
       val0 = val0<<1;
       val0++;                //shift bits up 1
       
     }
     else if( D0state ==0 && D1state == 1 )
     {
       //valid bit, logic 0 
       val0 = val0<<1;                    //shift bits up 1
     }
     else
     {
#ifdef debug
       Serial.print("RFID val0 failed D1state == D0state at loop index ");
       Serial.println(i,DEC);
       if(digitalReadFast(D0pin) == HIGH )
         Serial.println("D0state = 1");
       if(digitalReadFast(D1pin) == HIGH )
         Serial.println("D1state = 1");
       if(D0state)
         Serial.println("D0state = 1");
       if(D1state)
         Serial.println("D1state = 1");
#endif
       return 0;
     } 
     //waste time before the next bit
     //delayMicroseconds( uSBitPeriod );
   }  
    
   //next, read the seconds value (16-bits)  
   for( i=0;i<val1Bits;i++)
   {    
     
     //wait for signals to return to neutral
     timeout = 0;
     while( ( D0state != D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;
       
     }
     //next wait for next bit transition
     timeout = 0;
     while( ( D0state == D1state ) && !comTimeout )
     {
       D0state = digitalReadFast(D0pin);
       D1state = digitalReadFast(D1pin);  
       timeout++; 
       if( timeout > MAX_TIMEOUT )
         comTimeout = 1;  
     }
     
     
    //check that they are in a valid state, if not then give up
     if( D0state == 1 && D1state == 0 )
     {
       //valid bit is logic 1
       val1 = val1<<1;
       val1++;                //shift bits up 1
       
     }
     else if( D0state ==0 && D1state == 1 )
     {
       //valid bit, logic 0 
       val1 = val1<<1;                    //shift bits up 1
     }
     else
     {
#ifdef debug
       Serial.print("RFID val1 failed D1state == D0state at loop index ");
       Serial.println(i,DEC);
#endif
       return 0;
     }
     //waste time before the next bit
     //delayMicroseconds( uSBitPeriod );
     if( comTimeout )
     {
 #ifdef debug
       Serial.print(" RFIDread timed out on index ");
       Serial.println(i,DEC);
 #endif
     return 0;
    
     }   
   } 
   //if we have gotten this far, then we have 24 valid bits, we won't bother reading the last bit which is the
   //second parity bit 
   //calculate the result
#ifdef debug2
   Serial.println("Finished reading RFID");
   Serial.println(val0,DEC);
   Serial.println(val1,DEC);
#endif
   result = val0;
   result = result<<16;                    //shift the 1st value bits up to make room
   result += val1;
   return result;   
  
}
*/

/***********************************************************
* cmdProcess: this function processes the commands that have
* have already been parsed from the serial port
*
************************************************************/
#define ULOCK_TIME_MS 4000  //unlock for 1.5s  
#define UNLOCK 'U'          //unlock cmd value
#define LOCK   'L'          //lock cmd value
#define ADMIT_ACCESS  'A'   //temporarily unlock door
#define STATUS  'S'         //status cmd used by host to verify arduino is present

void cmdProcess()
{
  switch( cmd ){
    
  case UNLOCK:
#ifdef  debug
    Serial.print("UNLOCK door ");
    Serial.println( doorNum,DEC );
#endif
    //unlock the door corresponding with doorNum
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
      //direct access so we don't really need to update the updateTime etc
      digitalWriteFast( doorData[doorNum].pinNum, ULOCK_STATE );
      //record the new state
      doorData[doorNum].newState = ULOCK_STATE;
      
    }
    else  //fail
    {
      //give some bad feedback
    }
    break;
    
  case LOCK:
#ifdef  debug
    Serial.print("LOCK door ");
    Serial.println( doorNum,DEC );
#endif
    //unlock the door corresponding with doorNum
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
      //direct access so we don't really need to update the updateTime etc
      digitalWriteFast( doorData[doorNum].pinNum, LOCK_STATE );
      //record the new state
      doorData[doorNum].newState = LOCK_STATE;
    }
    else  //fail
    {
      //give some bad feedback
    }
    break; 
    
  case ADMIT_ACCESS:
#ifdef  debug
    Serial.print("GRANT door ");
    Serial.println( doorNum,DEC );
#endif
    //temporarily unlock door 
    if( doorNum < NUM_DOORS )            //make sure we don't go outside our array
    {
      
      //direct access so we don't really need to update the updateTime etc
      digitalWriteFast( doorData[doorNum].pinNum, ULOCK_STATE );
      //setup door to be locked in the future
      doorData[doorNum].newState = LOCK_STATE;
      doorData[doorNum].updateTime = millis() + ULOCK_TIME_MS;
    }
    else  //fail
    {
      //give some bad feedback
    }    
    break;
    
  case STATUS:
#ifdef  debug
    Serial.println("Get Status ");
#endif
    //respond with OK
    Serial.print(STATUS);
    Serial.println(",0,OK"); 
    break;
    
  default:
    //invalid command
    break; 
  }
}





/***********************************************************
* function to parse incomming serial stream for instructions
*
* format is command,data0(1 byte max),data1(10 byte max) \n
************************************************************/
#define BUF_LEN 10
char parseIndex = 0;                  //index to keep track of what step we are at
char inBuf[BUF_LEN];               //string buffer
char inByte = 0;                      //single byte tmp buffer
char lastByte = 0;                    //previous byte tmp buffer
//char cmd = 0;                         //var to store the cmd in
int data0 = 0;                        //var to store data0 for cmd
int data1 = 0;                        //var to store data1 for cmd (if exists
int j = 0;                            //index for keeping track of position in buffer
//long cardNum = 0;                     //variable to store the decoded card number
boolean parseSerial()
{
  
  while( Serial.available() )          //process all bytes in the buffer
  {
    inByte = Serial.read();            //read the next byte from the serial buffer
#ifdef debug2    
    Serial.print("\nparseIndex = ");
    Serial.println(parseIndex, DEC);
    Serial.print("inByte = ");
    Serial.println(inByte);
#endif
    //check for CR
    if( lastByte == '\n' || lastByte == ' ' )
    {
#ifdef debug2    
      Serial.print("\n");
      Serial.print("reset, inByte = ");
      Serial.println(inByte);
#endif
      parseIndex = 0;
    }
      
    if( parseIndex == 0 )
    {
#ifdef debug2
        Serial.println(" in ParseIndex = 0 function");
 #endif
        if( inByte != '\n' && inByte != ' ' && ( lastByte == '\n' || lastByte == ' ' ) )
        {
          //increment the parseIndex so that we can get along with it..
          parseIndex++;   
#ifdef debug2
          Serial.println("parseIndex++");
 #endif           
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 1 )
    {
        
        cmd = inByte;
        //increment the parse index, and break so that we read in the next byte
#ifdef debug
        Serial.print("\nreceived cmd byte: ");
        Serial.println(cmd);
#endif
        parseIndex++;
    }
    else if( parseIndex == 2 )
    {
        if( inByte != ',' && lastByte == ',' )
        {
          //increment the parseIndex so that we can get along with it..
#ifdef debug2
          Serial.print("\nreset index j");
#endif
          parseIndex++;   
          //reset buffer index
          j = 0;          
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 3 )
    {
        if( inByte >= '0' && inByte <= '9' )
        {
          inBuf[j] = inByte;         //add new byte
          j++;
          break;
        }
        else     //end of the data bytes
        {  
          //add a null character to end of inBuf
          inBuf[j] = NULL;
          //start by parsing the data array
          doorNum = atol( inBuf );
#ifdef debug
          Serial.print("\ndoor Num: ");
          Serial.print(doorNum,DEC);
          //Serial.write(inBuf,0);
          Serial.print("\n");
#endif

          //move on to the next step
          parseIndex++;
        }
    }
    if( parseIndex == 4 )
    {
        if( inByte != ',' && lastByte == ',' )
        {
          //increment the parseIndex so that we can get along with it..
#ifdef debug2
          Serial.print("\nreset index j");
#endif
          parseIndex++;   
          //reset buffer index
          j = 0;          
          //don't break out of switch() though, so that we loop through again with the same byte
        }
    }
    if( parseIndex == 5 )
    {
        if( inByte >= '0' && inByte <= '9' )
        {
          inBuf[j] = inByte;         //add new byte
          j++;
          break;
        }
        else     //end of the data bytes
        {  
          //add a null character to end of inBuf
          inBuf[j] = NULL;
          //start by parsing the data array
          cardNum = atol( inBuf );
#ifdef debug
          Serial.print("\nCard Num: ");
          Serial.print(cardNum,DEC);
          //Serial.write(inBuf,0);
          Serial.print("\n");
#endif

          //move on to the next step
          parseIndex++;
        }
    }
    if( parseIndex == 6 )
    {
        //if we got here, we have full success
        parseIndex = 0;  
        return true;    
    }
    
    //read next byte
    lastByte = inByte;    
    
  } 
  //if we got to here, we do not have fresh data
  return false;
  
}

/*************************************************************
* updateLock checks to see if a lock state needs to be changed,
* or, resets the millis() counter so that we never overrun the 
* counter
*************************************************************/

void updateLock()
{
  int i;
  //loop though the array
  for( i=0;i<NUM_DOORS;i++ )
  {
    //check if lock needs to be changed, timer is valid and expired
    if( doorData[i].updateTime < millis() && doorData[i].updateTime > 0 )
    {
      //udpate door state
      digitalWriteFast( doorData[i].pinNum, doorData[i].newState );
      //update the time so we don't repeat this
      doorData[i].updateTime = 0;
    }     
    
  }
  //loop through array, if all udpateTimes are invalid, reset millis() so we don't overrun
  for( i=0;i<NUM_DOORS;i++ )
  {
    //if any of the updateTimes are valid, then give up
    if( doorData[i].updateTime > 0 )
    {
      return;
    }     
    
  }
  //if we reached here then all updateTimes are invalid so we can reset millis()
  timer0_overflow_count = 0;
  
}
  




void loop()
{
  Serial.println("Listening for card");
  for(;;)
  {
    unsigned long cardValue = 0;
    //wait for either data pin to go low
    
    //check for changes from reader 0
    if( digitalReadFast( R0PinNum0 ) != digitalReadFast( R0PinNum1 ) )
    {
       //read incoming data on Reader 0        
      cardValue = readRFID( R0PinNum0, R0PinNum1 );
      //if we have read a valid card, then we need to tell the mother ship
      if( cardValue != 0 )
      {
        //send command showing that we have read a card
        Serial.print("R,0,");
        Serial.println(cardValue,DEC);
      }
#ifdef debugCardRead
      //otherswise if we did not receive a valid card number, send out error message
      else
      {
        Serial.println("E,0,Card read failed");
      }
#endif
       
    }
    //check reader 1
    else if( digitalReadFast( R1PinNum0 ) != digitalReadFast( R1PinNum1 ) )
    {
       //read incoming data on Reader 1        
      cardValue = readRFID( R1PinNum0, R1PinNum1 );
      //if we have read a valid card, then we need to tell the mother ship
      if( cardValue != 0 )
      {
        //send command showing that we have read a card
        Serial.print("R,1,");
        Serial.println(cardValue,DEC);
      }
#ifdef debugCardRead
      //otherswise if we did not receive a valid card number, send out error message
      else
      {
        Serial.println("E,1,Card read failed");
      }
#endif
       
    }
    //check reader 2
    else if( digitalReadFast( R2PinNum0 ) != digitalReadFast( R2PinNum1 ) )
    {
       //read incoming data on Reader 2        
      cardValue = readRFID( R2PinNum0, R2PinNum1 );
      //if we have read a valid card, then we need to tell the mother ship
      if( cardValue != 0 )
      {
        //send command showing that we have read a card
        Serial.print("R,2,");
        Serial.println(cardValue,DEC);
      }
#ifdef debugCardRead
      //otherswise if we did not receive a valid card number, send out error message
      else
      {
        Serial.println("E,2,Card read failed");
      }
#endif
       
    }i
    
    //process any waiting serial
    if( parseSerial() )
    {
      //execute the received command
      cmdProcess();
#ifdef debug
      //we have an updated command 
      Serial.print("\nnew command ");
      Serial.write( cmd );
      Serial.print("\n");
      Serial.print("door number ");
      Serial.println( doorNum,DEC );
      Serial.print("card number ");
      Serial.println( cardNum,DEC );
#endif
    }
  
    //check if we need to chagne the state of a lock
    updateLock();
    
  }
  
}

